Skip to content

refactor(worker-javascript): extract Phase 2 helpers from Core.js#1038

Merged
dqnykamp merged 8 commits intoDoenet:mainfrom
dqnykamp:core-refactor-2
May 3, 2026
Merged

refactor(worker-javascript): extract Phase 2 helpers from Core.js#1038
dqnykamp merged 8 commits intoDoenet:mainfrom
dqnykamp:core-refactor-2

Conversation

@dqnykamp
Copy link
Copy Markdown
Member

@dqnykamp dqnykamp commented May 1, 2026

Summary

Phase 2 of breaking up packages/doenetml-worker-javascript/src/Core.js. Same pattern as Phase 1 (composed sibling holding a core back-reference, thin delegating wrapper on Core for every public method/property). No behavior change.

Net effect over both phases: Core.js drops from 13,837 → 11,253 lines (-18.7%).

Phase 1 (#1036) is now merged. upstream/main has been merged into this branch so the diff against main shows only Phase 2 changes plus the // → managerName comment-style alignment described below.

Modules extracted in Phase 2

Module Lines Owns
RendererInstructionBuilder.ts 512 componentsToRender, componentsWithChangedChildrenToRender, rendererState; the dast/instruction stream sent to the renderer
ProcessQueue.ts 220 processQueue, processing, stopProcessingRequests; the async request queue and entry-point scheduling (executeProcesses, requestAction, requestUpdate, requestRecordEvent)
ComponentLifecycle.ts 264 Stateless: registerComponent, deregisterComponent, setAncestors, processNewDefiningChildren, spliceChildren, addChildrenAndRecurseToShadows
ChildMatcher.ts 483 Recursion guard only: child-group matching, adapter substitution, rendered-child filtering
DeletionEngine.ts 397 Stateless: two-phase component deletion
ActionTriggerScheduler.ts 303 stateVariableChangeTriggers, actionsChangedToActions, originsOfActionsChangedToActions; trigger polling and chained-action graph

Sequencing rationale

Per the multi-phase plan: RendererInstructionBuilder and ProcessQueue first (highest readability per line, unblock downstream); ChildMatcher and DeletionEngine are larger but algorithmically isolated; ActionTriggerScheduler last because it shares updateInfo fields with the perform bodies still in Core.

Carry-over from Phase 1 review

When upstream/main was merged into this branch, the Phase 1 review changes were preserved and the same conventions were extended to the Phase 2 wrappers:

  • All 6 Phase 2 wrapper section headers in Core.js use the // → managerName marker style introduced by Phase 1's review (replaces the longer multi-line "X lives in Y" comments).
  • Top-of-file overview comment now lists all Phase 2 modules alongside Phase 1.
  • Verified Phase 2 modules don't write to hasPendingDiagnostics directly (so Phase 1's removal of the set hasPendingDiagnostics accessor doesn't break them) and don't introduce any setTimeout/.then() chains that would need the new reportTimerError helper.

Deferred follow-ups

The deferred items in CORE_REFACTOR_DEFERRED.md (added by Phase 1) now also apply to Phase 2 — most notably:

  • Type the core: any back-reference in the new managers (RendererInstructionBuilder, ProcessQueue, ComponentLifecycle, ChildMatcher, DeletionEngine, ActionTriggerScheduler) — same shape as the Phase 1 managers.
  • Reduce stateless managers to plain functions: ComponentLifecycle, ChildMatcher, and DeletionEngine are stateless (only hold a core back-reference) and would fit the pure-function shape used in StateVariableNameResolver.ts.

Both are intentionally out of scope for this PR — better tackled as a single follow-up over both phases.

Test plan

  • npm run build -w @doenet/doenetml-worker-javascript passes
  • npm run build -w @doenet/doenetml passes
  • tsc --noEmit clean for @doenet/doenetml-worker-javascript
  • All 238 tests pass across diagnostics, copying, baseComponent, and answerValidation
    • Diagnostic paths exercise RendererInstructionBuilder (renderer state delivery includes diagnostics)
    • Copying tests heavily exercise ChildMatcher (alias/composite paths) and the new manager↔core back-refs
    • answerValidation exercises ActionTriggerScheduler (chained actions on submitAnswer)
  • CI: full Vitest suite + Cypress groups 1–5

🤖 Generated with Claude Code

dqnykamp and others added 5 commits May 1, 2026 14:14
Begin breaking up the 13,837-line Core class by lifting seven self-
contained, low-coupling helpers into TypeScript modules. The pattern
matches the existing composed siblings (Dependencies.js, ParameterStack):
each module is constructed with a `core` back-reference, and Core retains
a thin delegating wrapper for every method/property that was previously
on the class so external callers (CoreWorker, tests, components, and
`coreFunctions`-bound references) continue to work unchanged.

Modules extracted:
- DiagnosticsManager.ts       — diagnostics queue + source-location walk
- StateVariableNameResolver.ts — pure-function name resolution utilities
- VisibilityTracker.ts        — visibility state and save/suspend timers
- StatePersistence.ts         — save to localStorage / database
- AutoSubmitManager.ts        — debounced answer-submit queue
- NavigationHandler.ts        — handleNavigatingToComponent, navigateToTarget
- ResolverAdapter.ts          — adapter to the external Rust name resolver

No behavior change. Core.js drops from 13,837 to 12,909 lines.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Continues the Core.js breakup with six moderate-effort modules. Same
pattern as Phase 1 (composed sibling holding a `core` back-reference,
thin delegating wrapper on Core for every public method/property).
No behavior change.

Modules extracted:
- RendererInstructionBuilder.ts — owns componentsToRender,
  componentsWithChangedChildrenToRender, rendererState; the dast
  instruction stream sent to the renderer
- ProcessQueue.ts — owns processQueue, processing,
  stopProcessingRequests; async request queue and entry-point
  scheduling (executeProcesses, requestAction, requestUpdate,
  requestRecordEvent)
- ComponentLifecycle.ts — stateless: registration, ancestors,
  defining-child splicing, propagation to shadows
- ChildMatcher.ts — child-group matching, adapter substitution,
  rendered-child filtering (recursion guard only)
- DeletionEngine.ts — stateless two-phase component deletion
- ActionTriggerScheduler.ts — owns stateVariableChangeTriggers,
  actionsChangedToActions, originsOfActionsChangedToActions; trigger
  polling and chained-action graph

Core.js drops from 12,909 to 11,253 lines (this PR), 13,837 → 11,253
since the refactor began (~18.7%).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 1, 2026

⚠️ No Changeset found

Latest commit: 49f8fa1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

# Conflicts:
#	CLAUDE.md
#	packages/doenetml-worker-javascript/src/AutoSubmitManager.ts
#	packages/doenetml-worker-javascript/src/Core.js
#	packages/doenetml-worker-javascript/src/DiagnosticsManager.ts
#	packages/doenetml-worker-javascript/src/ResolverAdapter.ts
#	packages/doenetml-worker-javascript/src/StatePersistence.ts
#	packages/doenetml-worker-javascript/src/StateVariableNameResolver.ts
#	packages/doenetml-worker-javascript/src/VisibilityTracker.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

dqnykamp and others added 2 commits May 3, 2026 02:14
Replaces the open-coded three-field reset in `Core.generateDast` with
`processQueueManager.reset()`, matching the pattern used by
`RendererInstructionBuilder` and `ActionTriggerScheduler`.

Also updates `CORE_REFACTOR_DEFERRED.md` to extend the `core: any`
typing item and the stateless-managers item with the Phase 2 managers
(`RendererInstructionBuilder`, `ProcessQueue`, `ComponentLifecycle`,
`ChildMatcher`, `DeletionEngine`, `ActionTriggerScheduler`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documentation and small cleanups in the Phase 2 managers, plus deferred
items captured for follow-up:

- RendererInstructionBuilder: rewrite the misleading "(slated for later
  phases)" docstring (deriveChildResultsFromDefiningChildren and
  returnActiveChildrenIndicesToRender are in ChildMatcher this PR)
- All six new managers: add method-level docstrings on the non-obvious
  public methods; the top-of-module docs already covered ownership
- ProcessQueue: extract `_kickoff()` (was duplicated 3x in the request
  entry points), and attach `.catch(console.error)` to its
  `executeProcesses()` invocation per AGENTS.md
- ChildMatcher: convert `derivingChildResultsInProgress` from
  number[]-as-Set to a real `Set<number>`
- ComponentLifecycle: drop redundant `expandComposites: true` in the
  retry call (it's the default), drop `recursive === true`
- DeletionEngine: rewrite the broken-numbering "1./3./4." comment as a
  proper two-phase docstring; rename `compositeIdxStr` to
  `compositeIdx` (it's a number, not a string)
- All managers: standardize on `core._components` (was mixed with
  `core.components` getter, same array)

Deferred to CORE_REFACTOR_DEFERRED.md:
- Sweeping remaining fire-and-forget calls outside ProcessQueue
- Carried-over TODO/XXX markers (line numbers updated for new files)
- Renaming `processQueueManager` to `processQueue` (requires also
  dropping Core's `get/set processQueue` accessor that exposes the
  underlying array)
- The `_components`/`components` access convention (now consistent in
  Phase 2; document so future managers follow suit)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dqnykamp dqnykamp merged commit c809816 into Doenet:main May 3, 2026
17 checks passed
@dqnykamp dqnykamp deleted the core-refactor-2 branch May 3, 2026 08:22
dqnykamp added a commit to dqnykamp/DoenetML that referenced this pull request May 3, 2026
Brings in the squash-merged Phase 1 (Doenet#1036) and Phase 2 (Doenet#1038) PRs along
with their post-review changes, and adopts the same review-driven patterns
in the remaining Phase 3 modules.

Phase 1/2 manager files (ActionTriggerScheduler, AutoSubmitManager,
ChildMatcher, ComponentLifecycle, DeletionEngine, DiagnosticsManager,
ProcessQueue, RendererInstructionBuilder, ResolverAdapter, StatePersistence,
StateVariableNameResolver, VisibilityTracker) plus AGENTS.md, CLAUDE.md,
and the new utils/timerErrors.ts and CORE_REFACTOR_DEFERRED.md are taken
from upstream.

Core.js merges Phase 3's extraction wrappers with upstream's Phase 1/2
review changes:
- import * as nameResolver (replaces aliased named imports)
- short-form `// → managerName` markers replace the long per-section
  comment blocks
- top-of-class block comment now lists all phases
- removed `set hasPendingDiagnostics` (callers go through markPending())
- replaced open-coded processQueueManager three-field reset with
  processQueueManager.reset()
- added `// → componentBuilder`, `// → compositeExpander`,
  `// → stateVariableDefinitionFactory`, `// → stateVariableInitializer`,
  `// → resolverAdapter` markers for the new sections

Phase 3 modules adopt the Phase 2 review conventions:
- ComponentBuilder.ts: `core.hasPendingDiagnostics = true` →
  `core.diagnosticsManager.markPending()` (the setter on Core was removed
  in the Phase 1 review)
- ComponentBuilder.ts and CompositeExpander.ts: standardize on
  `core._components` (was using the `core.components` getter for the same
  underlying array)

CORE_REFACTOR_DEFERRED.md updates:
- Type-the-`core: any` and stateless-managers items now list Phase 3
  managers alongside Phase 1/2.
- Pre-existing fire-and-forget Core.js line numbers updated for the
  post-Phase-3 layout (444, 4387, 483-486).
- Carried-over TODO/XXX inventory now includes the Phase 3 markers in
  StateVariableInitializer, StateVariableDefinitionFactory,
  ComponentBuilder, and CompositeExpander.
- _components/components convention note now covers Phase 3.

Verification:
- `tsc --noEmit` produces strictly fewer errors than the pre-merge backup
  (359 vs 365 — the removed setter and the `core._components` switch
  resolve a handful of `any` complaints; nothing new was introduced).
- callAction.test.ts (20/20 — exercises StatePersistence + ProcessQueue +
  ActionTriggerScheduler), circle.test.ts and spreadsheet.test.ts
  (65 passed, 1 todo — exercises StateVariableInitializer's
  array-callback path that the Phase 3 follow-up commit fixed) all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants